import Cocoa

//var str = "Hello, playground"

/*
 1、闭包是自包含的功能代码块，跟C和Objective-C中的代码块(blocks)和其他一些语言中的匿名函数相似。
 2、闭包可以作为函数的参数也可以作为函数的返回值。
 3、可以像oc中用于回调和反向传值
 
 
 
 闭包表达式可以理解为闭包的表现形式
 语法形式为
 {
      (参数列表) -> 返回值类型 in 函数体代码
 }
 
 1. 参数可以有多个，多个参数用,隔开
 2. 参数列表的小括号可以省略
 3. 返回值类型也可以省略
 4. 当没有参数时in可以省略
 5. in可以看作是一个分隔符，将函数体和前面的参数、返回值分割开来

 
 */

//1.有参有返回值
let testOne:(String, String) -> String = {(str1, str2) -> String in
    return str1 + str2
}
print(testOne("test", "one"))
//表达式的右边是闭包的类型，=右边就是一个闭包的表达式，也可以理解为一个闭包。=右边是严格按照了闭包表达式来写的，有参数，有括号，有返回值。下面再看下闭包表达式的简写
//let testOne = { str1,str2 in
//    return str1 + str2
//}
//print(testOne("test", "one"))
//这个跟上一个是等价的，闭包表达式省去了参数的括号和返回值。:右边的闭包类型省去了是因为swift编译器能自动根据=右边去判断类型

//2.无参无返回值
let testThree: () -> Void = {
    print("testThree")
}
print(testThree())
//右边类型省去后
let testThree2 = {   //因为没有参数in可以省略
    print("testThree2---")
}
testThree2()



//闭包作为函数参数
//这个函数有三个参数，第一个参数是一个函数
func exec(fn: (Int, Int) -> Int, v1: Int, v2: Int) {
    print(fn(v1, v2))
}

//下面是exec函数的调用
exec(fn: { a, b in
    return a + b
}, v1: 1, v2: 2)
//exec函数调用时，{}里就是一个闭包表达式，可以看作是第一个参数函数的实现。这样的函数调用形式看起来很不友好，如果闭包表达式有很多行的话，会更加不友好，不利于代码的阅读。swift提供了一个尾随闭包的概念



/**
 尾随闭包:
 1. 当函数的最后一个参数是函数时，在函数调用时可以把闭包表达式写在()外面
 2. 尾随闭包是一个书写在函数括号之后的闭包表达式
 3. 如果将一个很长的闭包表达式作为函数的最后一个实参，使用尾随闭包可以增强函数的可读性
 */
func exec2(v1: Int, v2: Int, fn:(Int, Int) -> Int) {
   print(fn(v1, v2))
}
//这次把exec函数的第一个参数fn放到了最后，下面可以看下调用方式和上次的有什么不同
exec2(v1: 1, v2: 2) { a, b in
    return a + b
}
//跟上一次的函数调用对比，这次是把闭包表达式写在了函数括号之后，增强了代码的可读性，这就是尾随闭包。


//可以使用简化参数名，如$0, $1(从0开始，表示第i个参数...)
exec2(v1: 1, v2: 2) {
  return $0 + $1
}

//如果闭包表达式是函数的唯一实参，而且使用了尾随闭包的语法，可以将函数名后边的圆括号省略
func exec3(fn: (Int, Int) -> Int) {
    print(fn(1, 2))
}

exec3 { a, b in
    return a + b
}

/**
 逃逸闭包
 1. 如果一个闭包被作为一个函数的参数，并且在函数执行完之后才被执行，那么这种情况下的闭包就被称为逃逸闭包
 2. 在参数名的:后面用@escaping来修饰说明逃逸闭包
 3. 一般在涉及到异步操作时，闭包放在异步线程里，在这种情况下就会出现逃逸闭包，特别是在网络请求时会出现这种情况
 */
func exec4(fn: @escaping () -> ()) {
    //延迟5s
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) {
        //5秒后调用闭包
        fn()
    }
    print("函数执行完毕")
}
exec4 {
    print("闭包执行完毕")
}
//这段代码会先打印"函数执行完毕"，5秒后再执行闭包打印"闭包执行完毕"


/**
 自动闭包
 1. 在参数名的:后面用@autoclosure来修饰说明自动闭包
 2. @autoclosure会自动将20封装成闭包{20}
 3. @autoclosure只支持() -> T格式的参数
 4. @autoclosure并非只支持最后1个参数
 5. 有@autoclosure和无@autoclosure构成了函数重载
 6. 空合并运算符??使用了@autoclosure技术
 */

//先分析为什么会有自动闭包，自动闭包能实现什么作用
//先看下下面的一个函数
func getFirstPositive(_ a: Int, _ b: () -> Int) -> Int {
    return a > 0 ? a : b()
}
getFirstPositive(5) {  //使用了一个尾随闭包
    return 20
}
//getFirstPositive函数调用时使用了一个尾随闭包，当参数满足() -> T这个格式时可以写成自动闭包，会使代码阅读起来更直观
func getFirstPositive2(_ a: Int, _ b: @autoclosure () -> Int) -> Int {
    return a > 0 ? a : b()
}
getFirstPositive2(1, 10)

//需要注意的是闭包会有推迟执行的特点，会在函数内部调用时才会执行
func getFirstPositive(_ a: Int, _ b: @autoclosure () -> Int) -> Int {
    return a > 0 ? a : b()
}
func exec() -> Int {
    print("执行了exec")
    return 20
}
getFirstPositive(1, exec())
//看getFirstPositive(1, exec())函数调用时，很容易误以为exec()就已经执行了函数exec，其实并没有，exec内部没有执行，没有输出执行了exec。这是因为闭包有延迟执行的特点，getFirstPositive函数内部因为a>0返回的结果为a的值，并没有调用到b()，所以exec函数没有执行。只有当getFirstPositive函数内部调用到了b()，exec函数才会被执行


/**
 闭包对变量捕获:
 闭包可以对外部函数的变量\常量进行捕获
 闭包捕获时机是在函数执行完，return时再去捕获
 当函数里有多个闭包时，只会对变量\常量捕获一次，多个闭包对捕获的变量\常量共享
 闭包不会对全局变量进行捕获
 */

//下面由几份代码来说明这几个结论
//Mark:局部变量捕获
typealias fn = (Int) -> ()
func exec5() -> fn {
    var num = 0
    return { a in
        num += a
        print(num)
    }
}
let fn1 = exec5()
fn1(1)
fn1(2)
fn1(3)
//fn1、fn2、fn3输出的结果分别是1、3、6。
//这是一个函数中返回了一个闭包，在闭包里对num进行了累加并输出结果。

//1、第一次调用fn1时，num为0，0加上参数1=1
//2、第二次调用fn1时，闭包里的num的值是第一次fn1里的累加结果1，1加上参数2=3
//3、第三次调用fn1时，闭包里num是第二次fn1里累加的结果3，3加上参数3=6
//从三次调用fn1来看，闭包里num都是保存了上次调用后num的值，这是因为闭包捕获了外部的num，并重新在堆上分配了内存，当执行let fn1 = exec()时，把闭包的内存地址给了fn1,所以每次调用fn1都是调用的同一块内存，同一个闭包，闭包里有保存中捕获后的num的内存地址，所以每次调用都是同一个num

//可以把闭包想象成是一个类的实例对象，内存在堆空间，捕获的局部变量\常量就是对象的成员(存储属性)，组成闭包的函数就是类内部定义的方法

typealias fn2 = (Int) -> ()
func exec6() -> fn {
    var num = 0
    return {a in
        num += a
        print(num)
    }
}

let fn3 = exec6()
fn3(1)
let fn4 = exec6()
fn4(1)
//将上面的代码稍微改一下，将exec6分别赋值给fn3和fn4，输出的结果为1和1。这个为什么不是跟上面一样累加呢，因为exec6分别赋值给了fn3和fn4，fn3和fn4指向的是两个不一样的地址，当每调用一次exec6()函数，num会初始化为0



typealias fn5 = (Int) -> ()
func exec7() -> fn5 {
    var num = 0
    func plus(a: Int) {
        num += a
        print(num)
    }
    num = 6
    return plus
}
let f1 = exec7()
f1(1)
//这还是一个局部变量捕获的问题，闭包会在函数执行完，return的时候才会去捕获num，此时num已经由0变为6，所以执行f1(1)输出结果为7



typealias fn6 = (Int) -> ()
func exec8() -> (fn6, fn6) {   ///闭包作为函数的返回值
    var num = 0
    func plus2(a: Int) {
        num += a
        print("plus:", num)
    }
    func minus(a: Int) {
        num -= a
        print("minus:", num)
    }
    return (plus2, minus)
}
let (p, m) = exec8()
p(5)
m(4)
/*
 这份代码函数返回了一个元祖，元祖里是两个闭包，两个闭包里面都调用了num，输出的结果为：
 plus: 5
 minus: 1
 因为当函数里有多个闭包时，只会对变量\常量捕获一次，多个闭包对捕获的变量\常量共享
 */

//因为当函数里有多个闭包时，只会对变量\常量捕获一次，多个闭包对捕获的变量\常量共享。在调用m(4)时，前面已经调用过p(5)，此时num已经变为5，所以当调用m(4)，输出结果为1。














//闭包
//数组的排序
let names = ["zhangsan", "lisi", "wangwu", "zhaoliu"]
var sortedName = names.sorted()   //默认是正序排列
print(sortedName)

//现在需要将这个数组逆序排列
func backward(s1: String, s2: String) -> Bool {
    return s1 > s2
}

var reverseNames = names.sorted(by: backward)  //将函数作为另一个函数的参数
print(reverseNames)

//接着使用闭包表达式的形式来实现数组的逆序
//var reverseNames2 = names.sorted(by: {(s1: String, s2: String) -> Bool in
//    return s1 > s2
//})
//print(reverseNames2)


//因为排序闭包为实际参数来传递给函数,所以Swift可以推断出形式参数和返回值的类型
//var reverseNames2 = names.sorted(by: {s1, s2 in return s1 > s2})   //省略形式参数类型和返回值类型
//print(reverseNames2)

//但表达式闭包可以通过从他们的声明中去掉return,返回结果
//var reverseNames2 = names.sorted(by: {s1, s2 in s1 > s2})
//print(reverseNames2)

//简写实际参数名:可以使用$0,$1等名字来引用闭包的实际参数值
//var reverseNames2 = names.sorted(by: { $0 > $1})
//print(reverseNames2)


//也可以使用运算符函数来传递函数的实现
//var reverseNames2 = names.sorted(by: >)
//print(reverseNames2)


//尾随闭包:sorted后面的括号也给省略了
//var reverseNames2 = names.sorted{ $0 > $1}
//print(reverseNames2)

//捕获闭包
func makeIncrementer(amount: Int) -> () -> Int {
    var total = 0
    //再次定义一个内嵌函数,内嵌函数就是一个闭包
    func incrementer() -> Int {
        total += amount
        return total
    }
    return incrementer
}

//应该来说到return incrementer后面的大括号,定义的total这个变量的作用于就不在了,然而我多次调用makeIncrementer函数,total的值是会改变的.也就是说他在离开了tatal这个作用域后还是拿到了total的值
let incrementerByten = makeIncrementer(amount: 10)
print(incrementerByten())
print(incrementerByten())
print(incrementerByten())

//再次重新创建一个变量,total的初始值不会改变
let incrementerBySeven = makeIncrementer(amount: 7)
print(incrementerBySeven())
print(incrementerBySeven())


//因为闭包是引用类型,重新创建一个变量将之前的函数赋值给他,他是可以获取值的,他们指向的是同一个闭包.如果我再次打印incrementerByTen的值,这个值接着上面的值累加
let anotherIncrementer = incrementerByten
print(anotherIncrementer())
print(incrementerByten())
print(anotherIncrementer())



//逃逸闭包:当闭包作为一个实际参数传递给一个函数的时候,并且他会在函数返回之前调用,我们就说这个闭包逃逸了.
//
var completionHanders: [() -> Void] = []
print(completionHanders)
//逃逸闭包类型需要用@escaping标记
func someFunctionWithEscapingClosure(completionHander: @escaping() -> Void) {
    completionHanders.append(completionHander)
}

//让闭包@escaping意味着你必须要在闭包中显示的引用self
func someFunctionWithNoEscapingClosure(closure: () -> Void) {
    closure()
}

class SomeClass {
    var x = 10
    func doSomething() {
        someFunctionWithEscapingClosure { self.x = 100 }
        someFunctionWithNoEscapingClosure { x = 200 }
    }
}

let instance = SomeClass()
instance.doSomething()
print("x is \(instance.x)")

completionHanders.first?()
print(instance.x)


//自动闭包:自动闭包允许你延迟处理,因此闭包内部的代码直到你调用他的时候才会运行
//public func assert(_ condition: @autoclosure () -> Bool, message: @autoclosure () -> String = String(), file: StaticString = #file, line: UInt = #line)

//var costomerInLine = ["Chris", "Alex", "Ewa", "barray", "Daniella"]
////print(costomerInLine.count)
////
//var costomerProvider = { costomerInLine.remove(at: 0) }
////print(costomerInLine.count)
////
////print("Now serving \(costomerProvider())!")
////print(costomerInLine.count)
//
//
////func server(costomer costomerProvider: () -> String) {
////    print("Now serving \(costomerProvider())!")
////}
////
////server(costomer: { costomerInLine.remove(at: 0) } )
//
//
//func server(costomer costomerProvider: @autoclosure () -> String) {
//    print("Now serving \(costomerProvider())!")
//}
//server(costomer: costomerInLine.remove(at: 0))


//自动+逃逸:如果你想要自动闭包允许逃逸,就同时使用@autoclosure 和 @escaping 标志
//var costomerInLine = ["Chris", "Alex", "Ewa", "barray", "Daniella"]
//var customerProviders: [ () -> String] = []
//func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {
//    customerProviders.append(customerProvider)
//}
//collectCustomerProviders(costomerInLine.remove(at: 0))
//collectCustomerProviders(costomerInLine.remove(at: 0))
//print("collected \(customerProviders.count) closure")
//
//for customerProvider in customerProviders {
//    print("Now seving \(customerProvider())!")
//}




